<?php
/**
 * DMFramework
 * 
 * @license Copyright 2012 Dillen Meijboom under the Apache license version 2.0 (http://apache.org/licenses/LICENSE-2.0)
 * @author Dillen Meijboom <info@dillenm.nl>
 * @url http://dmframework.nl
 */

/**
 * The session-manager is a replacement for $_SESSION in PHP but it uses it's own manager
 * so you don't need to use session_start() at the beginning of your file.
 * 
 * @license Copyright 2012 Dillen Meijboom under the Apache license version 2.0 (http://apache.org/licenses/LICENSE-2.0)
 * @author Dillen Meijboom <info@dillenm.nl>
 * @package System.Library
 */
class Dmf_Sessions
{
    /**
     * Name of the session part
     * 
     * @var string
     */
    private $_name;
    
    /**
     * The session-id
     * 
     * @var int
     */
    private $_session_id;
    
    /**
     * All session storage
     * 
     * @var array
     */
    private $_session_storage = array();
    
    /**
     * The decoded file
     * 
     * @var null|array
     */
    private $_decoded_file;
    
    /**
     * All session manager instances
     * 
     * @var array
     */
    public static $instances = array();
    
    /**
     * Main configuration
     * 
     * @var array
     */
    private $_config = array(
        'encrypted' => true,
        'lifetime'  => 3600,
        'id_length' => 20,
        'save_path' => 'system/cache/sessions'
    );
    
    /**
     * Constructor
     * 
     * @param string $name
     * @throws Exception 
     */
    public function __construct( $name ) 
    {
        $this->_name = $name;
        
        if ( isset( Dmf_Application::getInstance()->getConfig()->asObject()->sessions ) )
        {
            $this->_config = array_merge( $this->_config, Dmf_Application::getInstance()->getConfig()->asObject()->sessions->asArray() );
        }
        
        if ( ! is_writable( $this->_config['save_path'] ) )
        {
            throw new Exception( 'The session cache is not writable.' );
        }
        
        if ( ! is_dir( $this->_config['save_path'] . '/' . $this->_name ) )
        {
            mkdir( $this->_config['save_path'] . '/' . $this->_name );
        }
        
        if ( $this->session_id() === null || ! file_exists( $this->getFilename() ) )
        {
            $this->createStorage();
        }
        else
        {
            $this->loadStorage();
        }
    }
    
    
    public function setOption( $index, $value )
    {
        $this->_config[ $index ] = $value;
    }
    
    /**
     * Initialize session manager
     * 
     * @param string $name
     * @return Dmf_Sessions
     */
    public static function init( $name )
    {
        if ( ! isset( self::$instances[ $name ] ) )
        {
            self::$instances[ $name ] = new Dmf_Sessions( $name );
        }
        
        return self::$instances[ $name ];
    }
    
    /**
     * Get a session manager instance
     * 
     * @param string $name
     * @return Dmf_Sessions
     */
    public static function getInstance( $name )
    {
        return self::init( $name );
    }
    
    /**
     * Create session UID
     * 
     * @return int
     */
    public function createUid()
    {
        $nr = '';
        
        for( $i = 0; $i < $this->_config['id_length']; $i++ )
        {
            $nr .= rand( 0, 9 );
        }
        
        return $nr;
    }
    
    /**
     * Get the filename
     * 
     * @return string
     */
    public function getFilename()
    {
        return $this->_config['save_path'] . '/' . $this->_name  .  '/' . sha1( $this->session_id() . '.ses' );
    }
    
    /**
     * Create session storage 
     */
    public function createStorage()
    {
        $sessid = $this->createUid();
        
        setCookie( 'DMF_SESSID_' . strtoupper( $this->_name ), htmlspecialchars( $sessid ), ( time() + $this->_config['lifetime'] ), '/', null, false, true );
        $this->_session_id = $sessid;
        
        $metadata = array(
            'time'          => time(),
            'user_agent'    => $_SERVER['HTTP_USER_AGENT'],
            'ip'            => $_SERVER['REMOTE_ADDR']
        );
        
        $gz_x = gzopen( $this->getFilename(), 'w9' );
        gzwrite( $gz_x, serialize( array(
            'metadata' => $metadata,
            'storage' => array()
        ) ) );
        gzclose( $gz_x );
    }
    
    /**
     * Get session id
     * 
     * @return int
     */
    public function session_id()
    {
        if ( $this->_session_id != null )
        {
            return $this->_session_id;
        }
        
        return ( isset( $_COOKIE[ 'DMF_SESSID_' . strtoupper( $this->_name ) ] ) ? $_COOKIE[ 'DMF_SESSID_' . strtoupper( $this->_name ) ] : null );
    }
    
    /**
     * Decode session file
     * 
     * @param string $filename
     * @return array
     */
    public function decode_file()
    {
        if ( $this->_decoded_file != null )
        {
            return $this->_decoded_file;
        }
        
        $this->_decoded_file = self::getContent( $this->getFilename() );
        
        return $this->_decoded_file;
    }
    
    /**
     * Get file content as array
     * 
     * @param string $filename
     * @return array
     */
    public static function getContent( $filename )
    {
        $unpack = unpack( 'V', substr( file_get_contents( $filename ) , -4 ) );
        $gz = gzopen( $filename, 'r' );
        
        return @unserialize( gzread( $gz, ( end( $unpack ) == 0 ? 1 : end( $unpack ) ) ) );
    }
    
    /**
     * Clear session storage 
     */
    public function clear()
    {
        if ( file_exists( $this->getFilename() ) )
        {
            @unlink(  $this->getFilename() );
        }
        
        if ( count( @scandir( dirname( $this->getFilename() ) ) ) == 2 )
        {
            @rmdir( dirname( $this->getFilename() ) );
        }
        
        setCookie( 'DMF_SESSID_' . strtoupper( $this->_name ), null, time() - 3600, '/' );
    }
    
    /**
     * Get session metadata
     * 
     * @return array
     */
    public function getMetadata()
    {
        $storage = $this->decode_file();
        
        return @$storage['metadata'];
    }
    
    /**
     * Get session metadata
     * 
     * @return array
     */
    public function getStorage()
    {
        $storage = $this->decode_file();
        
        return @$storage['storage'];
    }
    
    /**
     * Load session storage
     * 
     * @return null
     */
    public function loadStorage()
    {
        $metadata = $this->getMetadata();
        
        if ( $metadata['ip'] != $_SERVER['REMOTE_ADDR'] || $metadata['user_agent'] != $_SERVER['HTTP_USER_AGENT'] )
        {
            setCookie( 'DMF_SESSID_' . strtoupper( $this->_name ), null, time() - 3600, '/' );
            
            $this->_session_id = null;
            $this->_session_storage = array();
            
            $this->createStorage();
        }
        
        //Session lifetime check
        if ( $this->_config['lifetime'] != 0 && ( time() - $metadata['time'] ) > $this->_config['lifetime'] )
        {
            $this->clear();
            $this->__construct( $this->_name );
            return;
        }
        
        $this->_session_storage = $this->getStorage();
    }
    
    /**
     * Write var to session file
     * 
     * @param string $index
     * @param null|mixed $value 
     */
    public function write( $index, $value = null )
    {
        $this->_session_storage[ $index ] = $value;
    }
    
    /**
     * Read var from session file
     * 
     * @param string $index
     * @return null:mixed
     */
    public function read( $index )
    {
        return isset( $this->_session_storage[ $index ] ) ? $this->_session_storage[ $index ] : null;
    }
    
    /**
     * We will write to the session file only once in
     * stead of on every "write" call. 
     */
    public function __destruct()
    {
        if ( ! file_exists( $this->getFilename() ) )
        {
            return;
        }
        
        $metadata = array(
            'time'          => time(),
            'user_agent'    => $_SERVER['HTTP_USER_AGENT'],
            'ip'            => $_SERVER['REMOTE_ADDR']
        );
        
        $gz_x = gzopen( $this->getFilename(), 'w9' );
        gzwrite( $gz_x, serialize( array(
            'metadata' => $metadata,
            'storage' => $this->_session_storage
        ) ) );
        gzclose( $gz_x );
    }
}